/*
 *	MIDAS Context Based Routing
 *	cbr_daemon.cc
 *	Copyright (c) 2007 Bartlomiej Zolnierkiewicz
 *	MEAG IT PW
 */

#include <iostream>
#include <fstream>
#include <sstream>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

#include "scheduler.h"
#include "packet.h"

#include "fifo.h"

#include "cbr_config.h"
#include "cbr.h"
#include "cbr_packet.h"
#include "cbr_debug.h"

#include "cbr_address.h"
#include "cbr_acache.h"

#include "cbr_ip.h"
#include "cbr_eth.h"

#include "cbr_crt.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <getopt.h>

#include <linux/if.h> // for IFNAMSIZ
#include <linux/wireless.h>
#if WIRELESS_EXT < 10
# error "wireless extensions 10 or newer required"
#endif
#include <sys/ioctl.h>

using namespace std;

#define CBR_VERSION "0.9.89"

#define CBR_USE_RAW_IP 0

#define CBR_IN_PKTS_FIFO_SIZE	1000
#define CBR_OUT_PKTS_FIFO_SIZE	1000

fifo_t *cbr_in_pkts_fifo;
fifo_t *cbr_out_pkts_fifo;

pthread_mutex_t cbr_in_pkts_mutex;
pthread_cond_t cbr_in_pkts_cv;

pthread_mutex_t cbr_out_pkts_mutex;
pthread_cond_t cbr_out_pkts_cv;

/**
 * Checks if interface is in the managed mode.
 *
 * @param ifname interface name
 * @param managed reference to the managed flag
 *
 * Using SIOCGIWMODE ioctl from wireless extensions check in what mode
 * the interface is in.  If the ioctl fails it means that wireless
 * extensions are unsupported and most likely this is a wired interface.
 *
 * @return "true" on success, "false" otherwise.
 */

bool cbr_NetInterfaces::is_if_managed(const char *ifname, bool &managed) {
	struct iwreq wrq;

	int skfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (skfd == -1) {
cbr_log("error: cannot create socket for SIOCGIWMODE ioctl\n");
				close(skfd);
		return false;
	}

	memset(&wrq, 0, sizeof(wrq));

	// set device name
	strncpy(wrq.ifr_name, ifname, IFNAMSIZ);

	if (ioctl(skfd, SIOCGIWMODE, &wrq) != -1) {
		switch (wrq.u.mode) {
		case IW_MODE_ADHOC:
			managed = false;
			break;
		case IW_MODE_INFRA:
			break;
			managed = true;
		default:
cbr_log("error: interface %s is in the unsupported"
				" mode(%u)\n", ifname, wrq.u.mode);
						close(skfd);
			return false;
		}
	} else
		managed = true;

	close(skfd);

	return true;
}

/**
 * Add network interface.
 *
 * @param name name of the interface
 * @param force_mode force interace mode
 * @param mflag managed mode flag (valid if force_mode is set)
 *
 * Adds new network interface to the list of interfaces.
 *
 * @return "true" on success, "false" otherwise.
 */

bool cbr_NetInterfaces::add(const char *name, const char *ip_address, bool force_mode, bool mflag) {
	bool managed;

	if (force_mode)
		managed = mflag;
	else {
		if (is_if_managed(name, managed) == 0)
			// failed to check the interface
			return false;
	}

	cbr_log(" %s is in %s mode\n", name, managed ? "Managed" : "Ad-Hoc");

	ip_addr ip;

	ip = inet_addr(ip_address);
	
	int sock, index = -1;

#if CBR_USE_RAW_IP
	if (managed) {
		ip_addr ip;
		(void)get_if_ip_addr(name, &ip);
		sock = raw_ip_socket_create(name, ip);
	} else
#else
	sock = eth_socket_create(name, index);
#endif

	if (sock == -1) {
cbr_log		("error: cannot create socket for %s communication\n",
				managed ? "IP" : "RAW");
		return false;
	}

	cbr_log(" %s is using socket %d index %d\n", name, sock, index);

	cbr_NetworkInterface *netif =
	new cbr_NetworkInterface(name, managed, sock, index, ip);

	// add it to the list of interfaces
	interfaces.push_back(netif);

	return true;
}

/**
 * Gets IP address of the network interface.
 *
 * @param ifname interface name
 * @param ip pointer to the IP address
 *
 * Using SIOCGIFADDR ioctl gets the IP address of the network interface.
 *
 * @return "true" on success, "false" otherwise.
 */

bool cbr_NetInterfaces::get_if_ip_addr(const char *ifname, ip_addr *ip) {
	struct ifreq if_ipaddr;

	int skfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (skfd == -1) {
		cbr_log("error: cannot create socket for SIOCGIFADDR ioctl\n");
		return false;
	}

	memset(&if_ipaddr, 0, sizeof(if_ipaddr));

	// set device name
	strncpy(if_ipaddr.ifr_name, ifname, sizeof(if_ipaddr.ifr_name));

	if (ioctl(skfd, SIOCGIFADDR, &if_ipaddr)) {
		cbr_log("error: no IP address for %s\n", ifname);
		close(skfd);
		return false;
	}

	struct in_addr in;
	in = ((struct sockaddr_in *)&if_ipaddr.ifr_addr)->sin_addr;
	*ip = in.s_addr;

	cbr_log(" %s IP: %s\n", ifname, inet_ntoa(in));

	close(skfd);

	return true;
}

/**
 * Gets MAC address of the network interface.
 *
 * @param ifname interface name
 * @param mac pointer to the MAC address
 *
 * Using SIOCGIFHWADDR ioctl gets the MAC address of the network interface.
 *
 * @return "true" on success, "false" otherwise.
 */

bool cbr_NetInterfaces::get_if_hw_addr(const char *ifname, mac_addr *mac) {
	struct ifreq if_hwaddr;

	int skfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (skfd == -1) {
cbr_log		("error: cannot create socket for SIOCGIFHWADDR ioctl\n");
		return false;
	}

	memset(&if_hwaddr, 0, sizeof(if_hwaddr));

	// set device name
	strncpy(if_hwaddr.ifr_name, ifname, sizeof(if_hwaddr.ifr_name));

	if (ioctl(skfd, SIOCGIFHWADDR, &if_hwaddr)) {
		cbr_log("error: no MAC address for %s\n", ifname);
		close(skfd);
		return false;
	}

	*mac = *(struct ether_addr *)if_hwaddr.ifr_hwaddr.sa_data;

	cbr_log(" %s MAC: ", ifname);
	dumpmac(mac);

	close(skfd);

	return true;
}

bool send_msg = false;

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

static int do_shutdown = 0;

static void cbr_shutdown(int signal) {
	// TODO: do daemon cleanup here

cbr_log("daemon shutdown\n");

		Packet::purge();

	do_shutdown = 1;
}

static void cbr_sleep(double secs) {
	struct timespec interval;
	struct timespec remaining;
	unsigned long interval_usec;
	int err;

	interval_usec = (unsigned long)(secs * 1000000);
	interval.tv_sec = interval_usec / 1000000;
	interval.tv_nsec = interval_usec * 1000;

	/* fit it in the range or -EINVAL will happen */
	if (interval.tv_nsec < 0)
		interval.tv_nsec = 0;
	else if (interval.tv_nsec > 999999999)
		interval.tv_nsec = 999999999;

	while ((err = nanosleep(&interval, &remaining)) < 0) {
		if (err != -1 || errno != EINTR) {
			cbr_log("cbrd: nanosleep() error (errno=%d)", errno);
			do_shutdown = 1;
			break;
		}
	}
}

void schedule(ListScheduler *sch) {
if	(cbr_log_functions())
	cbr_log("in %s\n", __func__);

	for (;;) {
		if (do_shutdown)
		break;

		// proccess scheduled events which are "ready"
		sch->run();

		//		cbr_debug("going to sleep\n");
		cbr_sleep(SCHEDULE_INTERVAL);
	}
}

extern bool local_context_model;

/**
 * Sends outgoing data.
 *
 * @param agent CBR agent
 * @param s socket descriptor
 * @param i interface index
 * @param data data buffer
 * @param id node ID
 * @param use_ip IP encapsulation flag
 *
 * @return the  number of bytes sent on success and -1 on failure.
 */

int cbr_sendto(CBR *agent, int s, int i, void *data, cbr_ID id, bool use_ip) {
	int err = -1;

	if ((unsigned int)id == IP_BROADCAST)
	cbr_log("SEND: sending %s packet to <broadcast>\n",
			use_ip ? "IP" : "MAC");
	else
	cbr_log("SEND: sending %s packet to %d\n",
			use_ip ? "IP" : "MAC", id);

	if (use_ip) {
		ip_addr src_ip, dst_ip;
		string ip_str;

		// assumes that local ID -> IP address mapping exists
		src_ip = agent->ipcache.lookup(agent->getLocalID());

		/* temporary workaround for MDS/CKB problems
		 // use NIS for destination IP
		 if (local_context_model == false)
		 if (cbr_getNodeIP(id, ip_str) == -1)
		 cbr_log("NIS mapping failed for ID=%d\n", id);
		 */
		// cannot get NIS mapping - try local IP cache
		if (ip_str.empty()) {
cbr_log("using local \"NIS\" mapping\n");
						dst_ip = agent->ipcache.lookup(id);
		} else
			dst_ip = inet_addr(ip_str.c_str());

#if CBR_USE_RAW_IP
		err = raw_ip_send(s, src_ip, dst_ip, data);
#else
		mac_addr src_mac, dst_mac;

		// assumes that local ID -> MAC address mapping exists
		src_mac = agent->maccache.lookup(agent->getLocalID());

		// Central Node may not be in the same physical network
		// so always use broadcast
		if (agent->IsCentralNode())
			dst_mac = agent->maccache.lookup(IP_BROADCAST);
		else
			dst_mac = agent->cn.getMAC();

		err = eth_ip_send(s, i, &src_mac, &dst_mac, src_ip, dst_ip, data);
#endif
	} else {
		mac_addr src_mac, dst_mac;

		// assumes that local ID -> MAC address mapping exists
		src_mac = agent->maccache.lookup(agent->getLocalID());
		dst_mac = agent->maccache.lookup(id);
		err = eth_cbr_send(s, i, &src_mac, &dst_mac, data);
	}

	return err;
}

/**
 * Receives incoming data.
 *
 * @param agent CBR agent
 * @param s socket descriptor
 * @param data data buffer
 * @param use_ip IP encapsulation flag
 *
 * @return the number of bytes received on success and -1 on failure.
 */

static int cbr_recvfrom(CBR *agent, int s, void *data, bool use_ip) {
	cbr_ID id;
	int err;
	ip_addr src_ip = agent->ipcache.lookup(agent->getLocalID());

	memset(data, 0, cbr_Packet::maxSize);

	if (use_ip) {
		ip_addr *ip = new ip_addr;

#if CBR_USE_RAW_IP
		err = raw_ip_recv(s, data, ip);
#else
		mac_addr *mac = new mac_addr;

		err = eth_ip_recv(s, data, mac, ip, src_ip);
#endif
		if (err != -1) {
			id = (cbr_ID)*(u_int32_t *)((char *)data + 8);
			agent->ipcache.insert(id, *ip);
		}

		delete ip;
#if !CBR_USE_RAW_IP
		delete mac;
#endif
	} else {
		mac_addr *mac = new mac_addr;

		err = eth_cbr_recv(s, data, mac);

		if (err != -1) {
			id = (cbr_ID)*(u_int32_t *)((char *)data + 8);
			agent->maccache.insert(id, *mac);
		}

		delete mac;
	}

	return err;
}

/**
 * Checks for incoming data.
 *
 * @param agent CBR agent
 * @param s socket descriptor
 * @param data data buffer
 * @param use_ip IP encapsulation flag
 *
 * Checks socket for the incoming IP packets (when using IP socket)
 * or the incoming Ethernet frames (when using raw Ethernet socket).
 *
 * @return the number of bytes received on success, -1 otherwise.
 */

int cbr_select(CBR *agent, int s, void *data, bool use_ip) {
	fd_set read_fds;

	FD_ZERO(&read_fds);
	FD_SET(s, &read_fds);

	if (select(s + 1, &read_fds, NULL, NULL, NULL) == -1) {
		/* select() system call can be interrupted */
		if (errno != EINTR) {
			int errval= errno;
			char *errstr = strerror(errno);

cbr_log		("select() error: %d (%s)\n", errval, errstr);
	}
	return -1;
}

if (FD_ISSET(s, &read_fds))
return cbr_recvfrom(agent, s, data, use_ip);

return -1;
}

static void sendCBRMsg(CBR *agent, cbr_ContextAddress *addr, const char *msg,
		double delay) {
	u_int32_t trID = agent->getTransactionID();
	int len = strlen(msg) + 1;
	cbr_Msg *cbrmsg = new cbr_Msg(len, len, msg, addr, trID,
			cbr_Msg::RREQ, OUTGOING_MSG_LIFE);

	agent->sendDataMessage(cbrmsg, delay);
}

static void print_help(const char *name) {
	const char help_string[] = "\n"
		"Options:\n"
		"  -h, --help               display this help and exit\n"
		"  -i, --id                 CBR id\n"
		"  -q  --interface          Used interface address\n"
		"  -f, --firewalled         CN is behind a router/firewall\n"
		"  -a, --address            local context address\n"
		"  -n, --interfaces         network interfaces configuration file\n"
		"  -m, --mappings           NIS mappings configuration file\n"
		"  -o, --model              Context Model configuration file\n"
		"  -e, --cn-cfg             Central Node configuration file\n"
		"  -j, --cbr-to-crt-port    TCP port for CBR->CRT communication\n"
		"  -k, --crt-to-cbr-port    TCP port for CRT->CBR communication\n"
		"  -x, --localcontextmodel  use local context model\n"
		"  -l, --log                log filename\n"
		"  -c, --centralnode        operate as Central Node\n"
		"  -p, --packets            log content of all packets\n"
		"  -v, --verbose            log debug messages\n"
		"  -t, --target             destination context address\n"
		"  -s, --send               message to send\n"
		"  -d, --delay              delay for message to send (in seconds)\n"
		"  -1, --log-hello-packets  log content of CBR HELLO packets\n"
		"  -2, --log-route-packets  log content of CBR RREQ/RREP packets\n"
		"  -3, --log-data-packets   log content of CBR DATA packets\n"
		"  -4, --log-ip-packets     log content of IP packets\n"
		"  -5, --log-eth-packets    log content of ETH packets\n"
		"  -6, --log-dis-packets    log info about discarded packets\n"
		"  -7, --log-addresses      log IP and MAC addresses\n"
		"  -8, --log-functions      log function calls\n";

	printf("Context Based Routing (CBR) daemon (%s)\n\n", CBR_VERSION);
	printf("Usage: %s [-h]\n", name);
	printf("       %s -i <num> -a <str> -n <file> -m <file>\n", name);
	printf("       %s -o <file> -e <file> [-j <num>]\n", name);
	printf("       %s [-x] [-l <file>] [-c] [-p] [-v]\n", name);
	printf("       %s [-t <str>] [-s <str>] [-d <num>]\n", name);

	printf(help_string);
}

/**
 * Receive packets thread handler.
 *
 * @param ptr CBR agent pointer
 *
 * Receive thread handles data receival.
 */

void *recv_pkts_func(void *ptr) {
	CBR *agent = (CBR *)ptr;

	for (;;) {
if (cbr_log_functions())
			cbr_log("in %s\n", __func__);

				agent->netifs.recv(agent);

		if (do_shutdown)
			pthread_exit(NULL);
	}

	return NULL;
}

void *send_pkts_func(void *ptr) {
	CBR *agent = (CBR *)ptr;
	char *data;

	for (;;) {
if (cbr_log_functions())
			cbr_log("in %s\n", __func__);

				pthread_mutex_lock(&cbr_out_pkts_mutex);
		pthread_cond_wait(&cbr_out_pkts_cv, &cbr_out_pkts_mutex);
		pthread_mutex_unlock(&cbr_out_pkts_mutex);

		while (1) {
			pthread_mutex_lock(&cbr_out_pkts_mutex);
			data = (char *)fifo_dequeue(cbr_out_pkts_fifo);
			pthread_mutex_unlock(&cbr_out_pkts_mutex);

			if (data == NULL)
				break;

			if (agent->IsFirewalled()) {
				unsigned char msg_type = *(data + CBR_ID_SIZE);

				// forward all RREQ[1,2] & HELLO packets to CN
				if (!agent->IsCentralNode()) {
					switch (msg_type & CBRTYPE_MASK) {
					case CBRTYPE_HELLO:
					case CBRTYPE_RREQ1:
					case CBRTYPE_RREQ2:
						*(cbr_ID *)data = agent->cn.getID();
						break;
					}
				}

cbr_log("SEND: packet type=%02x next_hop=%d\n",
					msg_type, *(cbr_ID *)data);

								cbr_PutMsg(CBR_ID_SIZE + data, *(int *)data,
				cbr_Packet::maxSize);
			} else
				agent->netifs.send(agent, data);

			delete[] data;
		}

		if (do_shutdown)
			pthread_exit(NULL);
	}

	return NULL;
}

/**
 * HELLO messages thread handler.
 *
 * @param ptr CBR agent pointer
 *
 * Handles sending of CBR HELLO messages.
 *
 * Before sending CBR HELLO message this method obtains actual
 * Context Description from the MIDAS middleware and uses it to
 * update local node's Context Based Address used by CBR daemon.
 *
 * HELLO_INTERVAL define controls how often CBR HELLO messages
 * are sent (IOW for how long this thread is allowed to sleep).
 */

void *hello_func(void *ptr) {
	CBR *agent = (CBR *)ptr;

	for (;;) {
if (cbr_log_functions())
			cbr_log("in %s()\n", __func__);

				agent->setLocalCBA(cbr_getLocalCBA(agent->getLocalID()));
		agent->sendHello();

		cbr_sleep(HELLO_INTERVAL);

		if (do_shutdown)
			pthread_exit(NULL);
	}
}

/**
 * Send data messages thread handler.
 *
 * @param ptr CBR agent pointer
 */

void *data_msgs_func(void *ptr) {
	CBR *agent = (CBR *)ptr;

	for (;;) {
if (cbr_log_functions())
			cbr_log("in %s\n", __func__);

				cbr_GetMsg(agent);

		if (do_shutdown)
			pthread_exit(NULL);
	}
}

/**
 * Received packets processing thread handler.
 *
 * @param ptr CBR agent pointer
 *
 * Receive handler handles data receival.
 */

void *do_pkts_func(void *ptr) {
	CBR *agent = (CBR *)ptr;
	char *data;

	for (;;) {
if (cbr_log_functions())
			cbr_log("in %s\n", __func__);

				pthread_mutex_lock(&cbr_in_pkts_mutex);
		pthread_cond_wait(&cbr_in_pkts_cv, &cbr_in_pkts_mutex);
		pthread_mutex_unlock(&cbr_in_pkts_mutex);

		while (1) {
			pthread_mutex_lock(&cbr_in_pkts_mutex);
			data = (char *)fifo_dequeue(cbr_in_pkts_fifo);
			pthread_mutex_unlock(&cbr_in_pkts_mutex);

			if (data == NULL)
				break;

			agent->recv(data, NULL);

			delete[] data;
		}

		if (do_shutdown)
			pthread_exit(NULL);
	}

	return NULL;
}

int main(int argc, char *argv[]) {
	// command line options
	const struct option opts[] = { { "help", no_argument, NULL, 'h' }, { "id",
			required_argument, NULL, 'i' }, { "interface", required_argument,
			NULL, 'q' }, { "firewalled", no_argument, NULL, 'f' }, { "address",
			required_argument, NULL, 'a' }, { "interfaces", required_argument,
			NULL, 'n' }, { "mappings", required_argument, NULL, 'm' }, {
			"model", required_argument, NULL, 'o' }, { "cn-cfg",
			required_argument, NULL, 'e' }, { "cbr-to-crt-port",
			required_argument, NULL, 'j' }, { "crt-to-cbr-port",
			required_argument, NULL, 'k' }, { "localcontextmodel", no_argument,
			NULL, 'x' }, { "log", required_argument, NULL, 'l' }, {
			"centralnode", no_argument, NULL, 'c' }, { "packets", no_argument,
			NULL, 'p' }, { "verbose", no_argument, NULL, 'v' }, { "target",
			required_argument, NULL, 't' }, { "send", required_argument, NULL,
			's' }, { "delay", required_argument, NULL, 'd' }, {
			"log-hello-packets", no_argument, NULL, '1' }, {
			"log-route-packets", no_argument, NULL, '2' }, {
			"log-data-packets", no_argument, NULL, '3' }, { "log-ip-packets",
			no_argument, NULL, '4' }, { "log-eth-packets", no_argument, NULL,
			'5' }, { "log-dis-packets", no_argument, NULL, '6' }, {
			"log-addresses", no_argument, NULL, '7' }, { "log-functions",
			no_argument, NULL, '8' }, 
};

cbr_ID id = -1; // == 0xffffffff == IP_BROADCAST
			cbr_ContextAddress *addr = NULL, *dst_addr = NULL;
			string netifs_cfg_file, nis_cfg_file, model_cfg_file, cn_cfg_file,
			log, msg, ipAddress;
			bool firewalled = false, central_node = false;
			int delay = 0;
			/* ports used for CBR->CRT and CRT->CBR communication */
			int cbr_crt_port = -1, crt_cbr_port = -1;
			int rc;

			pthread_t recv_pkts_thread, send_pkts_thread, hello_thread,
			do_pkts_thread, data_msgs_thread;

			// print help and exit if there are no arguments
			if (argc == 1) {
				print_help(argv[0]);
				exit(0);
			}

			// parse command line options
			while (1) {
				int x = getopt_long(argc, argv,
						"hi:q:fa:n:m:o:e:j:k:xl:cpvt:s:d:12345678",
						opts, NULL);
				string str;

				// all options were parsed
				if (x == -1)
				break;

				switch (x) {
					case 'h':
					print_help(argv[0]);
					exit(0);
					break;
					case 'i':
					id = atoi(optarg);
					break;
					case 'q':
					ipAddress = optarg;
					break;
					case 'f':
					firewalled = true;
					break;
					case 'x':
					local_context_model = true;
					break;
					case 'j':
					cbr_crt_port = atoi(optarg);
					break;
					case 'k':
					crt_cbr_port = atoi(optarg);
					break;
					case 'a':
					str.assign(optarg);
					addr = new cbr_ContextAddress(str.c_str());
					str.clear();
					break;
					case 'n':
					netifs_cfg_file = optarg;
					break;
					case 'm':
					nis_cfg_file.assign(optarg);
					break;
					case 'o':
					model_cfg_file.assign(optarg);
					break;
					case 'e':
					cn_cfg_file.assign(optarg);
					break;
					case 'l':
					log.assign(optarg);
					break;
					case 'c':
					central_node = true;
					break;
					case 'p':
					log_packets = CBR_LOG_PACKETS_ALL;
					break;
					case 'v':
					extern bool debug_cbr;
					debug_cbr = true;
					break;
					case 't':
					str.assign(optarg);
					dst_addr = new cbr_ContextAddress(str.c_str());
					str.clear();
					break;
					case 's':
					// no binary messages from the command line
					msg.assign(optarg);
					break;
					case 'd':
					delay = atoi(optarg);
					break;
					case '1':
					log_packets |= CBR_LOG_PACKETS_CBR_HELLO;
					break;
					case '2':
					log_packets |= CBR_LOG_PACKETS_CBR_ROUTE;
					break;
					case '3':
					log_packets |= CBR_LOG_PACKETS_CBR_DATA;
					break;
					case '4':
					log_packets |= CBR_LOG_PACKETS_IP;
					break;
					case '5':
					log_packets |= CBR_LOG_PACKETS_ETH;
					break;
					case '6':
					log_packets |= CBR_LOG_PACKETS_DISCARDED;
					break;
					case '7':
					log_packets |= CBR_LOG_ADDRESSES;
					break;
					case '8':
					log_packets |= CBR_LOG_FUNCTIONS;
					break;
				}
			}

			// if not set use the default log filename
			if (log.empty())
			log.append("cbrd.log");

			cbr_Logger *logger = new cbr_Logger(log.c_str());
			// don't use cbr_log() here or gcc will complain ;)
			logger->log("CBR version %s - logging started\n", CBR_VERSION);

			if (id == -1) {
				cbr_log("error: no local ID given\n");
				exit(-1);
			}

			if (netifs_cfg_file.empty()) {
				cbr_log("error: no network interfaces configuration file given\n");
				exit(-1);
			}

			if (local_context_model == true) {
				if (addr == NULL) {
					cbr_log("error: no local context address given\n");
					exit(-1);
				}

				if (model_cfg_file.empty()) {
					cbr_log("error: no Context Model file given\n");
					exit(-1);
				}
			} else {
				if(ipAddress.empty()) {
					cbr_log("no ip address given for CBR->CRT communication, "
							"using the default value (127.0.0.1)\n");
					ipAddress.assign("127.0.0.1");
				}

				if (cbr_crt_port == -1) {
					cbr_log("no port given for CBR->CRT communication, "
							"using the default value (8888)\n");
					cbr_crt_port = 8888;
				} else if (cbr_crt_port <= 0 || cbr_crt_port> 65535) {
					cbr_log("error: invalid value (%d) for CBR->CRT"
							" communication port\n", cbr_crt_port);
					exit(-1);
				}

				if (cbr_connect_to_crt(cbr_crt_port, ipAddress) == 0) {
					cbr_log("error: cannot create socket for CBR->CRT "
							"communication\n");
					exit(-1);
				}

				if (crt_cbr_port == -1) {
					cbr_log("no port given for CRT->CBR communication, "
							"using the default value (8889)\n");
					crt_cbr_port = 8889;
				} else if (crt_cbr_port <= 0 || crt_cbr_port> 65535) {
					cbr_log("error: invalid value (%d) for CRT->CBR"
							" communication port\n", crt_cbr_port);
					exit(-1);
				}

				if (cbr_connect_to_crt2(crt_cbr_port, ipAddress) == 0) {
					cbr_log("error: cannot create socket for CRT->CBR "
							"communication\n");
					exit(-1);
				}

				cbr_log("using port %d for CBR->CRT communication\n",
						cbr_crt_port);

				cbr_log("using ip address %s for CBR->CRT communication\n",
						ipAddress.c_str());

				cbr_log("using port %d for CRT->CBR communication\n",
						crt_cbr_port);

				addr = cbr_getLocalCBA(id);
			}

			cbr_log(" local ID: %d\n", id);
			cbr_log(" local context address: %s\n", addr->getAsString().c_str());
			if (local_context_model)
			cbr_log(" using local context model\n");
			if (firewalled)
			cbr_log(" operating in \"firewalled\" mode\n");
			if (central_node)
			cbr_log(" operating as Central Node\n");
			if (log.empty() == 0)
			cbr_log(" log filename: %s\n", log.c_str());

			if (dst_addr || msg.empty() == 0) {

				if (dst_addr == NULL) {
					cbr_log("error: no destination context address given\n");
					exit(-1);
				}

				cbr_log(" destination context address: %s\n",
						dst_addr->getAsString().c_str());

				if (msg.empty()) {
					cbr_log("error: no message given\n");
					exit(-1);
				}

				cbr_log(" message: %s\n", msg.c_str());
				cbr_log(" delay: %ds\n", delay);

				// all options are OK - send the message and quit
				send_msg = true;
			}

			cbr_in_pkts_fifo = fifo_alloc(CBR_IN_PKTS_FIFO_SIZE);
			if (cbr_in_pkts_fifo == NULL) {
				cbr_log("cannot create incoming CBR packets queue\n");
				return -ENOMEM;
			}

			cbr_out_pkts_fifo = fifo_alloc(CBR_OUT_PKTS_FIFO_SIZE);
			if (cbr_out_pkts_fifo == NULL) {
				cbr_log("cannot create outgoing CBR packets queue\n");
				fifo_free(cbr_in_pkts_fifo);
				return -ENOMEM;
			}

			ListScheduler *scheduler = new ListScheduler;
			CBR *agent = new CBR(id, addr, netifs_cfg_file.c_str(),
					nis_cfg_file, model_cfg_file.c_str(),
					cn_cfg_file, firewalled, central_node);

			netifs_cfg_file.clear();
			nis_cfg_file.clear();

			// just to initialize ->instance_, there are no events yet
			scheduler->run();

			if (send_msg) {
				sendCBRMsg(agent, dst_addr, msg.c_str(), delay);
				delete dst_addr;
			}

			pthread_mutex_init(&cbr_in_pkts_mutex, NULL);
			pthread_cond_init(&cbr_in_pkts_cv, NULL);

			pthread_mutex_init(&cbr_out_pkts_mutex, NULL);
			pthread_cond_init(&cbr_out_pkts_cv, NULL);

			if (firewalled == 0) {
				rc = pthread_create(&recv_pkts_thread, NULL, recv_pkts_func,
						agent);
				if (rc) {
					cbr_log("cannot create recv_pkts_thread\n");
					return rc;
				}

				rc = pthread_detach(recv_pkts_thread);
				if (rc) {
					cbr_log("cannot detach recv_pkts_thread\n");
					return rc;
				}
			}

			rc = pthread_create(&send_pkts_thread, NULL, send_pkts_func, agent);
			if (rc) {
				cbr_log("cannot create send_pkts_thread\n");
				return rc;
			}

			rc = pthread_detach(send_pkts_thread);
			if (rc) {
				cbr_log("cannot detach send_pkts_thread\n");
				return rc;
			}

			rc = pthread_create(&do_pkts_thread, NULL, do_pkts_func, agent);
			if (rc) {
				cbr_log("cannot create do_pkts_thread\n");
				return rc;
			}

			rc = pthread_detach(do_pkts_thread);
			if (rc) {
				cbr_log("cannot detach do_pkts_thread\n");
				return rc;
			}

			// if this is the Central Node do not send HELLO packets
			if (central_node == 0) {
				cbr_debug("starting sedning HELLO packets\n");

				rc = pthread_create(&hello_thread, NULL, hello_func, agent);
				if (rc) {
					cbr_log("cannot create hello_thread\n");
					return rc;
				}

				rc = pthread_detach(hello_thread);
				if (rc) {
					cbr_log("cannot detach hello_thread\n");
					return rc;
				}
			}

			if (local_context_model == false) {
				// start sending packets
				rc = pthread_create(&data_msgs_thread, NULL, data_msgs_func,
						agent);
				if (rc) {
					cbr_log("cannot create data_msgs_thread\n");
					return rc;
				}

				rc = pthread_detach(data_msgs_thread);
				if (rc) {
					cbr_log("cannot detach data_msgs_thread\n");
					return rc;
				}
			}

			// enable purging of message table
			agent->mtimer.handle((Event*)0);

			// enable purging of routing table
			agent->crtimer.handle((Event*)0);

			cbr_debug("before daemon() call\n");

			signal(SIGINT, cbr_shutdown);
			signal(SIGTERM, cbr_shutdown);

			//	if (daemon(0, 0) < 0)
			//		cbr_log("error: daemon() failed\n");

			cbr_debug("after daemon() call\n");

			/* main thread (this one) processes events */
			schedule(scheduler);

			if (firewalled == 0)
			pthread_join(recv_pkts_thread, NULL);
			pthread_join(send_pkts_thread, NULL);
			pthread_join(do_pkts_thread, NULL);
			if (central_node == 0)
			pthread_join(hello_thread, NULL);
			if (local_context_model == false)
			pthread_join(data_msgs_thread, NULL);

			pthread_mutex_destroy(&cbr_in_pkts_mutex);
			pthread_cond_destroy(&cbr_in_pkts_cv);

			pthread_mutex_destroy(&cbr_out_pkts_mutex);
			pthread_cond_destroy(&cbr_out_pkts_cv);

			fifo_free(cbr_out_pkts_fifo);
			fifo_free(cbr_in_pkts_fifo);

			return 0;
		}
